package mnet

/*
 * mnet interfaces for user.
 */

import (
	"net"
	"sync"
	"time"
)

// Net event type
type dealerEventType int32

const (
	netDealerEventConnection dealerEventType = iota
)

// Connect event struct
type connectEvent struct {
	// Remote address
	connAddr string

	// Session
	session ISession

	// Packet codec.
	codec ICodec

	// Connect fail callback
	connErrCall func()
}

// Dealer event struct
type dealerEvent struct {
	dealType  dealerEventType
	connEvent connectEvent
}

// ISink represents a single goroutine user session.
type ISink interface {
	// Receiving message callback.
	OnRecv(msg []byte)

	// Establish or disconnect callback
	OnStatus(isConnected bool, session *SingleSession)
}

// create single goroutine session,
// that means all the clients run the tcp events in the same goroutine.
func NewSingleSession(sink ISink) *SingleSession {
	if sink == nil {
		return nil
	}
	return &SingleSession{
		conn:  nil,
		state: gClosedFlag,
		sink:  sink,
	}
}

// Net connection interface
type IConnection interface {
	// get connection id.
	GetConnID() uint32

	// get remote tcp address.
	RemoteAddr() net.Addr

	// get local tcp address
	LocalAddr() net.Addr

	// send message(thread safe) asynchronously.
	SendMsg(data []byte) error

	// send message timeout, sec is second unit.
	SendMsgTimeout(data []byte, sec int) error

	// send message synchronously
	SyncSend(data []byte) error

	// set related property.
	SetProperty(key string, value interface{})

	// get related property.
	GetProperty(key string) interface{}

	// remove related property
	RemoveProperty(key string)

	// get upper session
	GetSession() ISession

	// set connection's linger property.
	SetLinger(sec int) error

	// close connect with peer client.
	Close()

	// the following method can not visit outside.
	// wait to stop.
	Wait() bool

	// start worker thread
	start()

	// stop work
	stop()
}

// Connection manager interface
type IConnManager interface {
	// create IConnection
	create(net INet, conn *net.TCPConn, session ISession, codec ICodec) IConnection

	// remove IConnection
	remove(conn IConnection)

	// Get IConnection from id
	find(connID uint32) IConnection

	// Get len
	len() int

	// stop all connections.
	stop()

	// wait to stop all connections.
	wait()
}

// Net interface
type INet interface {
	// Init net with max conn size and max buff chan size.
	Init(maxConnSize uint32, log ILog, attach interface{}) bool

	// Create server, lockThread: whether lock this goroutine to a unique thread.
	CreateServer(localAddr string, sessFactory ISessionFactory, codec ICodec,
		attach interface{}, lockThread bool, maxConn int,
	) IServer

	// Create client
	CreateClient(remoteAddr string, session ISession, codec ICodec, attach interface{}) IClient

	// Get Attach
	GetAttach() interface{}

	// Stop the listen work.
	Stop()

	// Get all connection count.
	GetAllConnections() int

	// Run
	Run(count int) bool

	// sync Dial to remote addr
	syncDial(addr string, session ISession, codec ICodec) bool

	// get conn mgr
	getConnMgr() IConnManager

	// add event
	addDealerEvent(evt dealerEvent) bool
}

// tcp server interface
type IServer interface {
	// start listening.
	StartListen() bool

	// stop listening.
	Stop()

	// get attach
	GetAttach() interface{}
}

// tcp client interface
type IClient interface {
	// start to connect asynchronously without callback.
	StartConnect() bool

	// connect to server asynchronously with callback.
	AsynConnect(connErrCallback func()) bool

	// connect to server synchronously.
	SyncConnect() bool

	// get attach.
	GetAttach() interface{}

	// try to connect until it is connected.
	TryToConnect()
}

// The following interface must be implemented by user

// Message Pack/Unpack interface, which should be implemented by user.
// head + {msgId + data}
type ICodec interface {
	// Get packet head len.
	GetHeadLen() uint32

	// PacketParse parse from msgHead binary to get msg body's len or err if possible.
	PacketParse(msgHead []byte) (uint32, error)
}

// Net session definition which should be implemented by user
// OnEstablish(), OnRecv() and OnTerminate() all are all in multiple goroutine environment
// !! OnEstablish is first, then OnRecv(), but OnTerminate() will is the different goroutine !!
type ISession interface {
	// Recv message(msgId and data), data will push back to pool after this function.
	// msg如果push到别的协程处理，数据必须copy出来。
	OnRecv(msg []byte)

	// Establish connection callback
	// this function can't use a for {} to execute.
	OnEstablish()

	// Terminate connection callback
	OnTerminate()

	// !!!(Set IConnection, user must keep the conn pointer, and must not set conn to nil)
	SetConnection(conn IConnection)

	// Whether it is connected now for net reconnect check connection.
	IsConnected() bool
}

// Net session factory interface for server peer, which should be implemented by user.
type ISessionFactory interface {
	// create session, it is in multiple goroutine.
	CreateSession() ISession
}

// rpc message struct(encode/decode with gob)
type RPCMessageInfo struct {
	// function name
	FuncName string

	// function parameter list.
	Para []interface{}
}

// create big codec
func CreateBigCodec() ICodec {
	return &gMyMsgCodec
}
func CreateBigCodecWithSize(size uint32) ICodec {
	if size <= 0 {
		size = gMaxPackSize
	}
	return &BigMsgCodec{maxPacketSize: size}
}

/*
*  Creating common session also can use
*  Session{
*	  DealerFunc:*,
*	  EstablishFunc: *,
*	  Attach: *,
*   }
*  outside.
 */
func CreateSession() *Session {
	return &Session{conn: nil, state: gClosedFlag}
}

// Create sync_session.
func CreateSyncSession() *SyncSession {
	syncSess := &SyncSession{
		conn:      nil,
		state:     gClosedFlag,
		nextObjId: 0,
		pool: &sync.Pool{
			New: func() interface{} {
				return make(chan response)
			},
		},
		timeOutDuration: 3 * time.Second,
	}
	syncSess.init()
	return syncSess
}

// =======internal log interface==========
// log interface
type ILog interface {
	Info(v ...interface{})
	Warn(v ...interface{})
	Error(v ...interface{})
	Debug(v ...interface{})
}

// you can use the following log to debug.
type myLog struct{}

func (mg *myLog) Info(v ...interface{}) {
	//_, _ = fmt.Println(v...)
}
func (mg *myLog) Warn(v ...interface{}) {
	//_, _ = fmt.Println(v...)
}
func (mg *myLog) Error(v ...interface{}) {
	//_, _ = fmt.Println(v...)
}
func (mg *myLog) Debug(v ...interface{}) {
	//_, _ = fmt.Println(v...)
}

// Global inner log interface
var innerLog ILog

// mnet inner log() function to get log module
func log() ILog {
	if innerLog == nil {
		innerLog = &myLog{}
	}
	return innerLog
}

// Set log interface.
func SetLog(log ILog) {
	innerLog = log
}

// set read/write buffer.
func SetWriteBufferSize(size int) {
	if size > 0 {
		gWriteBufferMaxSize = size
	}
}
func SetReadBufferSize(size int) {
	if size > 0 {
		gReadBufferMaxSize = size
	}
}

func init() {
	innerLog = nil
}
